/*
 * Copyright (C) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.talkback.contextmenu;

import android.content.Context;
import android.content.DialogInterface;
import android.graphics.PointF;
import android.view.Menu;

import com.android.talkback.R;
import com.android.utils.SparseIterableArray;

/**
 * Implements a radial menu with up to four corners.
 *
 * @see Menu
 */
public class RadialMenu extends ContextMenu {
    private static final int ORDER_NW = 0;
    private static final int ORDER_NE = 1;
    private static final int ORDER_SW = 2;
    private static final int ORDER_SE = 3;

    private final DialogInterface mParent;
    private final SparseIterableArray<RadialMenuItem> mCorners;

    private RadialMenuItem.OnMenuItemSelectionListener mSelectionListener;
    private OnMenuVisibilityChangedListener mVisibilityListener;
    private MenuLayoutListener mLayoutListener;

    /**
     * Creates a new radial menu with the specified parent dialog interface.
     *
     * @param context Current context
     * @param parent to the menu
     */
    public RadialMenu(Context context, DialogInterface parent) {
        super(context);
        mParent = parent;
        mCorners = new SparseIterableArray<>();
    }

    public void setLayoutListener(MenuLayoutListener layoutListener) {
        mLayoutListener = layoutListener;
    }

    /**
     * Sets the default selection listener for menu items. If the default
     * selection listener returns false for an event, it will be passed to the
     * menu's parent menu (if any).
     *
     * @param selectionListener The default selection listener for menu items.
     */
    public void setDefaultSelectionListener(
            RadialMenuItem.OnMenuItemSelectionListener selectionListener) {
        mSelectionListener = selectionListener;
    }

    public void setOnMenuVisibilityChangedListener(OnMenuVisibilityChangedListener listener) {
        mVisibilityListener = listener;
    }

    @Override
    public RadialMenuItem add(int groupId, int itemId, int order, CharSequence title) {
        final RadialMenuItem item = new RadialMenuItem(getContext(), groupId, itemId, order, title);
        return addItem(item);
    }

    /**
     * Add a pre-constructed {@link RadialMenuItem} to the menu.
     *
     * @param item The item to add.
     */
    private RadialMenuItem addItem(RadialMenuItem item) {
        // If this is a sub-menu, add the default selection and click listeners.
        if (item.hasSubMenu()) {
            item.setOnMenuItemSelectionListener(mSelectionListener);
            item.setOnMenuItemClickListener(getDefaultListener());
        }

        if (item.getGroupId() == R.id.group_corners) {
            item.setCorner();
            mCorners.put(item.getOrder(), item);
        } else {
            add(item);
        }

        onLayoutChanged();

        return item;
    }

    /**
     * Adds all items from a collection of {@link RadialMenuItem}s to the menu.
     *
     * @param items The items to add.
     */
    public void addAll(Iterable<? extends RadialMenuItem> items) {
        for (RadialMenuItem item : items) {
            addItem(item);
        }
    }

    @Override
    public RadialSubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
        final RadialSubMenu submenu = new RadialSubMenu(
                getContext(), mParent, this, groupId, itemId, order, title);

        addItem(submenu.getItem());

        return submenu;
    }

    /*package*/ void onShow() {
        if (mVisibilityListener != null) {
            mVisibilityListener.onMenuShown();
        }
    }

    /*package*/ void onDismiss() {
        if (mVisibilityListener != null) {
            mVisibilityListener.onMenuDismissed();
        }
    }

    @Override
    public void close() {
        onDismiss();
        mParent.dismiss();
    }

    @Override
    public ContextMenuItemBuilder getMenuItemBuilder() {
        return new ContextMenuItemBuilder() {
            @Override
            public ContextMenuItem createMenuItem(Context context, int groupId, int itemId,
                                                  int order, CharSequence title) {
                return new RadialMenuItem(context, groupId, itemId, order, title);
            }
        };
    }

    @Override
    public RadialMenuItem getItem(int index) {
        return (RadialMenuItem) super.getItem(index);
    }

    /**
     * Gets the corner menu item with the given group ID.
     *
     * @param groupId The corner group ID of the item to be returned. One of:
     *            <ul>
     *            <li>{@link RadialMenu#ORDER_NW}
     *            <li>{@link RadialMenu#ORDER_NE}
     *            <li>{@link RadialMenu#ORDER_SE}
     *            <li>{@link RadialMenu#ORDER_SW}
     *            </ul>
     * @return The corner menu item.
     */
    public RadialMenuItem getCorner(int groupId) {
        return mCorners.get(groupId);
    }

    /**
     * Returns the rotation of a corner in degrees.
     *
     * @param groupId The corner group ID of the item to be returned. One of:
     *            <ul>
     *            <li>{@link RadialMenu#ORDER_NW}
     *            <li>{@link RadialMenu#ORDER_NE}
     *            <li>{@link RadialMenu#ORDER_SE}
     *            <li>{@link RadialMenu#ORDER_SW}
     *            </ul>
     * @return The rotation of a corner in degrees.
     */
    /* package */ static float getCornerRotation(int groupId) {
        final float rotation;

        switch (groupId) {
            case RadialMenu.ORDER_NW:
                rotation = 135;
                break;
            case RadialMenu.ORDER_NE:
                rotation = -135;
                break;
            case RadialMenu.ORDER_SE:
                rotation = -45;
                break;
            case RadialMenu.ORDER_SW:
                rotation = 45;
                break;
            default:
                rotation = 0;
        }

        return rotation;
    }

    /**
     * Returns the on-screen location of a corner as percentages of the
     * screen size. The resulting point coordinates should be multiplied by
     * screen width and height.
     *
     * @param groupId The corner group ID of the item to be returned. One of:
     *            <ul>
     *            <li>{@link RadialMenu#ORDER_NW}
     *            <li>{@link RadialMenu#ORDER_NE}
     *            <li>{@link RadialMenu#ORDER_SE}
     *            <li>{@link RadialMenu#ORDER_SW}
     *            </ul>
     * @return The on-screen location of a corner as percentages of the
     *         screen size.
     */
    static PointF getCornerLocation(int groupId) {
        final float x;
        final float y;

        switch (groupId) {
            case RadialMenu.ORDER_NW:
                x = 0;
                y = 0;
                break;
            case RadialMenu.ORDER_NE:
                x = 1;
                y = 0;
                break;
            case RadialMenu.ORDER_SE:
                x = 1;
                y = 1;
                break;
            case RadialMenu.ORDER_SW:
                x = 0;
                y = 1;
                break;
            default:
                return null;
        }

        return new PointF(x, y);
    }

    @Override
    public void removeItem(int id) {
        super.removeItem(id);
        onLayoutChanged();
    }

    /**
     * Performs the selection action associated with a particular
     * {@link RadialMenuItem}.
     *
     * @param item to select
     * @param flags unused
     * @return {@code true} if the menu item performs an action
     */
    public boolean selectMenuItem(RadialMenuItem item, int flags) {
        return ((item != null) && item.onSelectionPerformed())
                || ((mSelectionListener != null) && mSelectionListener.onMenuItemSelection(item));

    }

    public boolean clearSelection(int flags) {
        return selectMenuItem(null, flags);
    }

    @Override
    public void removeGroup(int group) {
        super.removeGroup(group);
        onLayoutChanged();
    }

    @Override
    public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
        super.setGroupCheckable(group, checkable, exclusive);
        onLayoutChanged();
    }

    @Override
    public void setGroupEnabled(int group, boolean enabled) {
        super.setGroupEnabled(group, enabled);
        onLayoutChanged();
    }

    @Override
    public void setGroupVisible(int group, boolean visible) {
       super.setGroupVisible(group, visible);
        onLayoutChanged();
    }

    @Override
    public RadialMenuItem findItem(int id) {
        RadialMenuItem menuItem = (RadialMenuItem) super.findItem(id);
        if (menuItem == null) {
            for (RadialMenuItem item : mCorners) {
                if (item.getItemId() == id) {
                    return item;
                }
            }
        }

        return menuItem;
    }

    void onLayoutChanged() {
        if (mLayoutListener != null) {
            mLayoutListener.onLayoutChanged();
        }
    }

    public interface MenuLayoutListener {
        void onLayoutChanged();
    }

    /**
     * Interface definition for a callback to be invoked when a menu is shown or
     * dismissed.
     */
    public interface OnMenuVisibilityChangedListener {
        /**
         * Called when a menu has been displayed.
         */
        void onMenuShown();

        /**
         * Called when a menu has been dismissed.
         */
        void onMenuDismissed();
    }
}
